﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ScreenServer
{
    public class MulticastClient
    {
        public event EventHandler<BufferEventArgs> DataReceived;
        protected virtual void OnDataReceived(BufferEventArgs e)
        {
            if (DataReceived != null) DataReceived(this, e);
        }
        private readonly Socket _socket;
        private readonly byte[] _buffer;
        public IPAddress Group { get; private set; }
        public int Port { get; private set; }
        public bool IsRunning { get; private set; }
        private bool _keepRunning;
        public MulticastClient(IPAddress group, int port)
        {
            Group = group;
            Port = port;
            _socket =
                new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Dgram,
                    ProtocolType.Udp);
            _socket.Bind(
                new IPEndPoint(IPAddress.Any, Port));
            _socket.SetSocketOption(
                SocketOptionLevel.IP,
                SocketOptionName.AddMembership,
                new MulticastOption(Group, IPAddress.Any));
            _buffer = new byte[0xFFFFFF];
        }
        private readonly byte[] _buf = new byte[0xFFFFFF];
        private int _bufOffset;
        public void Start()
        {
            if (IsRunning) return;
            IsRunning = true;
            _keepRunning = true;
            _socket.BeginReceive(_buf, _bufOffset, _buf.Length-_bufOffset, SocketFlags.None, ReceivedData, _socket);
            //new Thread(ReceiveData) { IsBackground = true}.Start();
        }

        private void ReceivedData(IAsyncResult ar)
        {
            var len = _socket.EndReceive(ar);
            var offset = _bufOffset;
            _bufOffset += len;
            if (_bufOffset + 2000 > _buf.Length)
                _bufOffset = 0;
            _socket.BeginReceive(_buf, _bufOffset, _buf.Length - _bufOffset, SocketFlags.None, ReceivedData, _socket);
            Parse(_buf, offset, len);
        }

        private readonly byte[] _magic = new byte[] {0x09, 0x33, 0x00, 0x34, 0x52, 0x10};
        private readonly List<int> _packets = new List<int>();
        private ushort _seq;
        private int _totalLength, _totalPacket;
        private DateTime _lastRx;
        private void Parse(byte[] buffer, int offset, int length)
        {
            //MagicValue, seq, TotalLength, TotalPacket, PacketNum, Reserved
            //6,2,4,4,4,12
            if (length < 33) return;
            if (!IsMatch(_magic, buffer, offset)) return;
            var seq = BitConverter.ToUInt16(buffer, offset + 6);
            var totalLength = BitConverter.ToInt32(buffer, offset + 8);
            var totalPacket = BitConverter.ToInt32(buffer, offset + 12);
            var packetNum = BitConverter.ToInt32(buffer, offset + 16);
            if (packetNum > totalPacket) return;
            var time = DateTime.Now.Subtract(_lastRx);
            _lastRx = DateTime.Now;

            if (_packets.Count < 1 || time > TimeSpan.FromSeconds(10) || seq > _seq)
                Reset(seq, totalLength, totalPacket);
            StoreData(packetNum, buffer, offset + 32, length - 32);
        }

        private void StoreData(int packetNum, byte[] buffer, int offset, int length)
        {
            if (!_packets.Remove(packetNum)) return;
            var p = packetNum << 10;
            Buffer.BlockCopy(buffer, offset, _buffer, p, length);
            if (_packets.Count > 0) return;
            OnDataReceived(new BufferEventArgs(_buffer, 0, _totalLength));
        }
        private void Reset(ushort seq, int totalLength, int totalPacket)
        {
            _seq = seq;
            _totalLength = totalLength;
            _totalPacket = totalPacket;
            _packets.Clear();
            for (var i = 0; i < _totalPacket; i++)
                _packets.Add(i);
        }
        private static bool IsMatch(byte[] pattern, byte[] array, int offset)
        {
            if ((array.Length - offset) < pattern.Length) return false;
            for (var i = 0; i < pattern.Length; i++)
                if (pattern[i] != array[i]) return false;
            return true;
        }
        public void Stop()
        {
            try
            {
                _keepRunning = false;
                _socket.Close(1000);
            }
            catch (Exception)
            {   
            }
        }
    }
}